#!/bin/bash

#------------------------------------------------------------------------------
# Modifies the volume according to the passed argument and uses the ratpoison
# window manager to draw a volume volume bar. By default increases volume when
# called.
# 
# Dependencies:
# 	- amixer and/or pulsemixer (used to fetch and set audio value)
# 	- ratpoison
#
# Notes:
#	- So that ratpoison can output unicode while we use unicode sequences, we
#	  need to assign potential unicode characters using printf -v
#	- When the script is executed multiple times in a row (say, when the user is
#	  quickly changing the volume), ratpoison would not properly restore the
#	  bar gravity to what it was. To avoid this, we prefer to exit early if a
#	  specific lockfile (/tmp/ratvol.lock) exists.
#	- Reasoning for disabling set -u:
#	  Seting a variable declared as an integer (declare -i) to a string,
#	  causes it to have value 0. This script (ab)uses this behaviour for
#	  error handling, since it saves us the use of two regexes calls to
#	  validate user input for the barsize and step arguments. Since set -u
#	  conflicts with this behaviour (it raises variable unbound error on string
#	  assigments to integer variables), I have disabled it.
#------------------------------------------------------------------------------

set -e -o pipefail

# Desc:
# 	Stop script execution and output a foramtted error message.
# Args:
# 	$@: string
# 		contents of the error message
die() {
	local err_msg="ratvol: ${@}"
	ratpoison -c "echo ${err_msg}" # Also print error message in ratpoison
	printf '%s\n' "${err_msg}"
	printf "Try ratvol -h for more information\n"
	exit 1;
}

usage() {
	cat <<-USAGE_END
		Usage: ratvol [OPTIONS] [+]
		Modifies the master volume, by default decreasing it, and draws a volume
		bar using ratpoison.

		-c, --character [character]	Character used to fill volume bar, it must be only one
		-g, --gravity [gravity]		Where the volume bar should be drawn Valid positions are:
		 				nw, w, sw, n, c, s, ne, e, se
		-s, --step [step]		How much the volume the volume step should be
		-p, --percentage		Show the percentage level
		-t, --msgtime			Duration that the volume bar will stay on screen
		-h, --help			Prints this message
	USAGE_END
	exit ${1}
}

# Desc: 
# 	Draws a volume bar using ratpoison.
# Args:
# 	$1: int volume
# 		The percentage of the current volume level
# 	$2:	enum(nw|w|sw|n|c|s|ne|e|se) gravity
# 		Where should ratpoison draw the bar (the gravity)
# 	$3: bool show_percentage
# 		Whether or not to show the percentage value in the bar
# 	$4: char fill_char
# 		The character used to fill the volume bar
# 	$5: int barsize
# 		The size of the volume bar
# 	$6: int max_vol
# 		The maximum volume that audio backend allows
# 	$7: int msgtime
# 		The amount of time the volume bar stays on the screen
# 	$8: int icon
# 		The icon, if any, that should appear in the beginning of the bar
drawbar() {
	local -r volume=${1} gravity=${2} show_percentage=${3} 
	local -r fill_char=${4} barsize=${5} max_vol=${6} msgtime=${7}
	local -r old_gravity=$(ratpoison -c 'set bargravity')
	local -r old_msgtime=$(ratpoison -c 'set msgwait')
	local -r filled=$((barsize * volume / max_vol))
	
	# Format volbar according to icon
	local volbar
	# Escape the '%' so printf can output it
	[[ ${8} == '%' ]] && local -r icon="%${8}"
	if [[ -z ${icon} ]] ; then
		# Empty if there is no icon
		local volbar=""
	else
		# Use printf to handle unicode
		printf -v volbar "${icon} "
	fi

	for ((i=0 ; i < barsize; i++)) ; do
		# Draw the percentage	
		if ${show_percentage} && ((i == barsize / 2 - 2 )); then
			volbar+="[${volume}%]"
		fi
		# Draw the filled part of the volume bar
		if ((i < filled)) ; then
			volbar+=${fill_char}
			continue
		fi
		# Fill the remainder with empty space
		volbar+=' '
	done

	# Prepare ratpoison for drawing
	ratpoison -c "set bargravity ${gravity}"
	ratpoison -c "set msgwait ${msgtime}"
	
	# Draw the bar
	ratpoison -c "echo ${volbar}"
	
	# Restore changed settings
	ratpoison -c "set bargravity ${old_gravity}"
	ratpoison -c "set msgwait ${old_msgtime}"
}

main() {
	# Handles race issues when various calls are made in quick sucession
	local -r lockfile='/tmp/ratvol.lock'
	[[ -e ${lockfile} ]] && exit
	trap "rm ${lockfile}" EXIT
	touch ${lockfile}

	local -r volregex='[[:digit:]]*(?=%)'
	local -r block_char='\u2588'

	local -i barsize=34
	local -i step=5
	local -i max_vol=100

	printf -v icon '\u266a'
	printf -v character "${block_char}"
	msgtime=1
	show_percentage=false
	gravity='nw'
	op='-'

	short_opts='hpg:s:S:t:i:c:'
	long_opts='help,show-percentage,no-icon,gravity:,step:,barsize:,msgtime:,character:'
	parsed=$(getopt --options ${short_opts} --longoptions ${long_opts} -n 'compiled' -- "${@}")

	[[ $? != 0 ]] && die 'Try ratvol -h for more information'

	eval set -- "${parsed}"

	while true; do
		case "${1}" in
			-g|--gravity)
				gravity="${2}"
				shift 2
			;;
			-p|--percentage)
				show_percentage=true
				shift
			;;
			-s|--step)
				step="${2}"
				shift 2
			;;
			-i|--icon)
				icon="${2}"
				shift 2
			;;
			-t|--msgtime)
				msgtime="${2}"
				shift 2
			;;
			-t|--step)
				step="${2}"
				shift 2
			;;
			-S|--barsize)
				barsize="${2}"
				shift 2
			;;
			-c|--character)
				printf -v character "${2}"
				shift 2
			;;
			--)
				shift
				break
			;;
			-h|--help) usage 0 ;;
			*) 
				echo 'Internal error.' >&2
				exit 1
			;;
		esac
	done

	# Input validation
	[[ ${gravity} != @(nw|w|sw|n|c|s|ne|e|se) ]] && die "Bad value for gravity: ${gravity}"
	((${#character} != 1)) && die "Character must have a length of 1: ${character}"
	((${step} <= 0)) && die "Step can't be less than 0: ${step}"
	((${barsize} <= 0)) && die "Barsize can't be less than 0: ${barsize}"
	((${msgtime} <= 0)) && die "Msgtime can't be less than 0: ${msgtime}"

	# Handle invalid volume operation and extra invalid arguments
	if ((${#} >= 1)) ; then
		((${#} > 1)) && die "Unrecognized arguments: ${@}"
		if [[ ${1} == '+' ]] ; then
			op='+'
		elif [[ ${1} == '-' ]] ; then
			: # nop
		else
			die "Unrecognized argument: ${1}"
		fi
	fi

	# Check if pulsemixer exists
	if [[ -n $(type -P pulsemixer) ]] ; then
		volume="$(pulsemixer --change-volume ${op}5 --get-volume)"
		# pulsemixer gives two audio levels, we only need one, so we cut the second
		volume=${volume% *}
		max_vol=150
	else
		volume="$(amixer set Master ${step}%${op} | grep -Po ${volregex})"
	fi
	drawbar\
		${volume} ${gravity} ${show_percentage}\
		${character} ${barsize} ${max_vol}\
		${msgtime} ${icon}
}
main $@
